/* 
 * code generator for runtime LPC code
 */
#include "std.h"
#include "lpc_incl.h"
#include "icode.h"
#include "compiler.h"
#include "generate.h"

static void ins_real(double);
static void ins_short(short);
static void upd_short(int, int, char *);
static void ins_byte(unsigned char);
static void upd_byte(int, unsigned char);
static void write_number(int);
static void ins_int(int);
#if SIZEOF_PTR == 8
static void ins_long(long);
#endif
void i_generate_node(parse_node_t *);
static void i_generate_if_branch(parse_node_t *, int);
static void i_generate_loop
        (int, parse_node_t *, parse_node_t *, parse_node_t *);
static void i_update_branch_list(parse_node_t *, char *);
static int try_to_push(int, int);

static int foreach_depth = 0;

static int current_num_values;

static int last_size_generated;
static int line_being_generated;

static int push_state;
static int push_start;

static parse_node_t *branch_list[3];
static int nforward_branches, nforward_branches_max;
static int *forward_branches = 0;

static void ins_real(double l)
{
	float f = (float) l;

	if (prog_code + 4 > prog_code_max)
	{
		mem_block_t *mbp = &mem_block[A_PROGRAM];

		UPDATE_PROGRAM_SIZE;
		realloc_mem_block(mbp);

		prog_code = mbp->block + mbp->current_size;
		prog_code_max = mbp->block + mbp->max_size;
	}STORE_FLOAT(prog_code, f);
}

/*
 * Store a 2 byte number. It is stored in such a way as to be sure
 * that correct byte order is used, regardless of machine architecture.
 * Also beware that some machines can't write a word to odd addresses.
 */
static void ins_short(short l)
{
	if (prog_code + 2 > prog_code_max)
	{
		mem_block_t *mbp = &mem_block[A_PROGRAM];
		UPDATE_PROGRAM_SIZE;
		realloc_mem_block(mbp);

		prog_code = mbp->block + mbp->current_size;
		prog_code_max = mbp->block + mbp->max_size;
	}STORE_SHORT(prog_code, l);
}

/*
 * Store a 4 byte number. It is stored in such a way as to be sure
 * that correct byte order is used, regardless of machine architecture.
 */
static void ins_int(int l)
{

	if (prog_code + 4 > prog_code_max)
	{
		mem_block_t *mbp = &mem_block[A_PROGRAM];
		UPDATE_PROGRAM_SIZE;
		realloc_mem_block(mbp);

		prog_code = mbp->block + mbp->current_size;
		prog_code_max = mbp->block + mbp->max_size;
	}STORE_INT(prog_code, l);
}

/*
 * Store a 8 byte number. It is stored in such a way as to be sure
 * that correct byte order is used, regardless of machine architecture.
 */
#if SIZEOF_PTR == 8
static void ins_long(long l)
{
	if (prog_code + 8 > prog_code_max)
	{
		mem_block_t *mbp = &mem_block[A_PROGRAM];
		UPDATE_PROGRAM_SIZE;
		realloc_mem_block(mbp);

		prog_code = mbp->block + mbp->current_size;
		prog_code_max = mbp->block + mbp->max_size;
	}
	STORE_PTR(prog_code, l);
}
#endif

static void upd_short(int offset, int l, char * where)
{
	unsigned short s;

	IF_DEBUG(UPDATE_PROGRAM_SIZE);DEBUG_CHECK2(offset > CURRENT_PROGRAM_SIZE,
			"patch offset %x larger than current program size %x.\n",
			offset, CURRENT_PROGRAM_SIZE);
	if (l > USHRT_MAX)
	{
		char buf[256];

		sprintf(buf, "branch limit exceeded in %s, near line %i", where,
		        line_being_generated);
		yyerror(buf);
	}
	s = l;

	COPY_SHORT(mem_block[A_PROGRAM].block + offset, &s);
}

static void ins_rel_short(int l)
{
	if (l > USHRT_MAX)
	{
		char buf[256];

		sprintf(buf, "branch limit exceeded in switch table, near line %i",
		        line_being_generated);
		yyerror(buf);
	}
	ins_short(l);
}

static void ins_byte(unsigned char b)
{
	if (prog_code == prog_code_max)
	{
		mem_block_t *mbp = &mem_block[A_PROGRAM];
		UPDATE_PROGRAM_SIZE;
		realloc_mem_block(mbp);

		prog_code = mbp->block + mbp->current_size;
		prog_code_max = mbp->block + mbp->max_size;
	}
	*prog_code++ = b;
}

static void upd_byte(int offset, unsigned char b)
{
	IF_DEBUG(UPDATE_PROGRAM_SIZE);DEBUG_CHECK2(offset > CURRENT_PROGRAM_SIZE,
			"patch offset %x larger than current program size %x.\n",
			offset, CURRENT_PROGRAM_SIZE);
	mem_block[A_PROGRAM].block[offset] = b;
}

static void end_pushes(void)
{
	if (push_state)
	{
		if (push_state > 1)
			upd_byte(push_start, push_state);
		push_state = 0;
	}
}

static void initialize_push(void)
{
	int what = mem_block[A_PROGRAM].block[push_start];
	int arg = mem_block[A_PROGRAM].block[push_start + 1];

	prog_code = mem_block[A_PROGRAM].block + push_start;
	ins_byte(F_PUSH);
	push_start++; /* now points to the zero here */
	ins_byte(0);

	switch (what)
	{
		case F_CONST0:
			ins_byte(PUSH_NUMBER | 0);
			break;
		case F_CONST1:
			ins_byte(PUSH_NUMBER | 1);
			break;
		case F_BYTE:
			ins_byte(PUSH_NUMBER | arg);
			break;
		case F_SHORT_STRING:
			ins_byte(PUSH_STRING | arg);
			break;
		case F_LOCAL:
			ins_byte(PUSH_LOCAL | arg);
			break;
		case F_GLOBAL:
			ins_byte(PUSH_GLOBAL | arg);
			break;
	}
}

/*
 * Generate the code to push a number on the stack.
 * This varies since there are several opcodes (for
 * optimizing speed and/or size).
 */
static void write_small_number(int val)
{
	if (try_to_push(PUSH_NUMBER, val))
		return;
	ins_byte(F_BYTE);
	ins_byte(val);
}

static void write_number(int val)
{
	if ((val & ~0xff) == 0)
		write_small_number(val);
	else
	{
		end_pushes();
		if (val < 0 && val > -256)
		{
			ins_byte(F_NBYTE);
			ins_byte(-val);
		}
		else if (val >= -32768 && val <= 32767)
		{
			ins_byte(F_SHORT_INT);
			ins_short(val);
		}
		else
		{
			ins_byte(F_NUMBER);
			ins_int(val);
		}
	}
}

static void generate_expr_list(parse_node_t * expr)
{
	parse_node_t *pn;
	int n, flag;

	if (!expr)
		return;
	pn = expr;
	flag = n = 0;
	do
	{
		if (pn->type & 1)
			flag = 1;
		i_generate_node(pn->v.expr);
		n++;
	}
	while ((pn = pn->r.expr));

	if (flag)
	{
		pn = expr;
		do
		{
			n--;
			if (pn->type & 1)
			{
				end_pushes();
				ins_byte(F_EXPAND_VARARGS);
				ins_byte(n);
			}
		}
		while ((pn = pn->r.expr));
	}
}

static void generate_lvalue_list(parse_node_t * expr)
{
	while ((expr = expr->r.expr))
	{
		i_generate_node(expr->l.expr);
		end_pushes();
		ins_byte(F_VOID_ASSIGN);
	}
}

INLINE_STATIC void switch_to_line(int line)
{
	int sz = CURRENT_PROGRAM_SIZE - last_size_generated;
	ADDRESS_TYPE s;
	unsigned char *p;

	if (sz)
	{
		s = line_being_generated;

		last_size_generated += sz;
		while (sz > 255)
		{
			p = (unsigned char *) allocate_in_mem_block(A_LINENUMBERS,
			        sizeof(ADDRESS_TYPE) + 1);
			*p++ = 255;
#if !defined(USE_32BIT_ADDRESSES) && !defined(LPC_TO_C)
			STORE_SHORT(p, s);
#else
			STORE_INT(p, s);
#endif
			sz -= 255;
		}
		p = (unsigned char *) allocate_in_mem_block(A_LINENUMBERS,
		        sizeof(ADDRESS_TYPE) + 1);
		*p++ = sz;
#if !defined(USE_32BIT_ADDRESSES) && !defined(LPC_TO_C)
		STORE_SHORT(p, s);
#else
		STORE_INT(p, s);
#endif
	}
	line_being_generated = line;
}

static int try_to_push(int kind, int value)
{
	if (push_state)
	{
		if (value <= PUSH_MASK)
		{
			if (push_state == 1)
				initialize_push();
			push_state++;
			ins_byte(kind | value);
			if (push_state == 255)
				end_pushes();
			return 1;
		}
		else
			end_pushes();
	}
	else if (value <= PUSH_MASK)
	{
		push_start = CURRENT_PROGRAM_SIZE;
		push_state = 1;
		switch (kind)
		{
			case PUSH_STRING:
				ins_byte(F_SHORT_STRING);
				break;
			case PUSH_LOCAL:
				ins_byte(F_LOCAL);
				break;
			case PUSH_GLOBAL:
				ins_byte(F_GLOBAL);
				break;
			case PUSH_NUMBER:
				if (value == 0)
				{
					ins_byte(F_CONST0);
					return 1;
				}
				else if (value == 1)
				{
					ins_byte(F_CONST1);
					return 1;
				}
				ins_byte(F_BYTE);
		}
		ins_byte(value);
		return 1;
	}
	return 0;
}

void i_generate_node(parse_node_t * expr)
{
	if (!expr)
		return;

	if (expr->line && expr->line != line_being_generated)
		switch_to_line(expr->line);
	switch (expr->kind)
	{
		case NODE_FUNCTION:
		{
			unsigned short num;

			/* In the following we know that this function wins all the */
			/* rest  - Sym                                              */

			num = FUNCTION_TEMP(expr->v.number)->u.index;
			FUNC(num)->address = generate_function(FUNC(num), expr->r.expr,
			        expr->l.number);
			break;
		}
		case NODE_TERNARY_OP:
			i_generate_node(expr->l.expr);
			expr = expr->r.expr;
		case NODE_BINARY_OP:
			i_generate_node(expr->l.expr);
			/* fall through */
		case NODE_UNARY_OP:
			i_generate_node(expr->r.expr);
			/* fall through */
		case NODE_OPCODE:
			end_pushes();
			ins_byte(expr->v.number);
			break;
		case NODE_TERNARY_OP_1:
			i_generate_node(expr->l.expr);
			expr = expr->r.expr;
			/* fall through */
		case NODE_BINARY_OP_1:
			i_generate_node(expr->l.expr);
			i_generate_node(expr->r.expr);
			end_pushes();
			ins_byte(expr->v.number);
			ins_byte(expr->type);
			break;
		case NODE_UNARY_OP_1:
			i_generate_node(expr->r.expr);
			/* fall through */
		case NODE_OPCODE_1:
			if (expr->v.number == F_LOCAL)
			{
				if (try_to_push(PUSH_LOCAL, expr->l.number))
					break;
			}
			else if (expr->v.number == F_GLOBAL)
			{
				if (try_to_push(PUSH_GLOBAL, expr->l.number))
					break;
			}
			end_pushes();
			ins_byte(expr->v.number);
			ins_byte(expr->l.number);
			break;
		case NODE_OPCODE_2:
			end_pushes();
			ins_byte(expr->v.number);
			ins_byte(expr->l.number);
			if (expr->v.number == F_LOOP_COND_NUMBER
			)
				ins_int(expr->r.number);
			else
				ins_byte(expr->r.number);
			break;
		case NODE_RETURN:
		{
			int n;
			n = foreach_depth;
			end_pushes();
			while (n--)
				ins_byte(F_EXIT_FOREACH);

			if (expr->r.expr)
			{
				i_generate_node(expr->r.expr);
				end_pushes();
				ins_byte(F_RETURN);
			}
			else
				ins_byte(F_RETURN_ZERO);
			break;
		}
		case NODE_STRING:
			if (try_to_push(PUSH_STRING, expr->v.number))
				break;
			if (expr->v.number <= 0xff)
			{
				ins_byte(F_SHORT_STRING);
				ins_byte(expr->v.number);
			}
			else
			{
				ins_byte(F_STRING);
				ins_short(expr->v.number);
			}
			break;
		case NODE_REAL:
			end_pushes();
			ins_byte(F_REAL);
			ins_real(expr->v.real);
			break;
		case NODE_NUMBER:
			write_number(expr->v.number);
			break;
		case NODE_LAND_LOR:
			i_generate_node(expr->l.expr);
			i_generate_forward_branch(expr->v.number);
			i_generate_node(expr->r.expr);
			if (expr->l.expr->kind == NODE_BRANCH_LINK)
				i_update_forward_branch_links(expr->v.number, expr->l.expr);
			else
				i_update_forward_branch("&& or ||");
			break;
		case NODE_BRANCH_LINK:
			i_generate_node(expr->l.expr);
			end_pushes();
			ins_byte(0);
			expr->v.number = CURRENT_PROGRAM_SIZE;
			ins_short(0);
			i_generate_node(expr->r.expr);
			break;
		case NODE_CALL_2:
			generate_expr_list(expr->r.expr);
			end_pushes();
			ins_byte(expr->v.number);
			ins_byte(expr->l.number >> 16);
			ins_short(expr->l.number & 0xffff);
			ins_byte((expr->r.expr ? expr->r.expr->kind : 0));
			break;
		case NODE_CALL_1:
			generate_expr_list(expr->r.expr);
			end_pushes();
			if (expr->v.number == F_CALL_FUNCTION_BY_ADDRESS)
			{
				expr->l.number = comp_def_index_map[expr->l.number];
			}
			ins_byte(expr->v.number);
			ins_short(expr->l.number);
			ins_byte((expr->r.expr ? expr->r.expr->kind : 0));
			break;
		case NODE_CALL:
			generate_expr_list(expr->r.expr);
			end_pushes();
			ins_byte(expr->v.number);
			ins_short(expr->l.number);
			break;
		case NODE_TWO_VALUES:
			i_generate_node(expr->l.expr);
			i_generate_node(expr->r.expr);
			break;
		case NODE_CONTROL_JUMP:
		{
			int kind = expr->v.number;
			end_pushes();
			ins_byte(F_BRANCH);
			expr->v.expr = branch_list[kind];
			expr->l.number = CURRENT_PROGRAM_SIZE;
			ins_short(0);
			branch_list[kind] = expr;
			break;
		}
		case NODE_PARAMETER:
		{
			int which = expr->v.number + current_num_values;
			if (try_to_push(PUSH_LOCAL, which))
				break;
			ins_byte(F_LOCAL);
			ins_byte(which);
			break;
		}
		case NODE_PARAMETER_LVALUE:
			end_pushes();
			ins_byte(F_LOCAL_LVALUE);
			ins_byte(expr->v.number + current_num_values);
			break;
		case NODE_IF:
			i_generate_if_branch(expr->v.expr, 0);
			i_generate_node(expr->l.expr);
			if (expr->r.expr)
			{
				i_generate_else();
				i_generate_node(expr->r.expr);
			}
			i_update_forward_branch("if");
			break;
		case NODE_LOOP:
			i_generate_loop(expr->type, expr->v.expr, expr->l.expr,
			        expr->r.expr);
			break;
		case NODE_FOREACH:
		{
			int tmp = 0;

			i_generate_node(expr->v.expr);
			end_pushes();
			ins_byte(F_FOREACH);
			if (expr->l.expr->v.number == F_GLOBAL_LVALUE
			)
				tmp |= FOREACH_RIGHT_GLOBAL;
			else if (expr->l.expr->v.number == F_REF_LVALUE
			)
				tmp |= FOREACH_REF;
			if (expr->r.expr)
			{
				if (tmp & FOREACH_RIGHT_GLOBAL
				)
					tmp = (tmp & ~FOREACH_RIGHT_GLOBAL) | FOREACH_LEFT_GLOBAL;

				tmp |= FOREACH_MAPPING;
				if (expr->r.expr->v.number == F_GLOBAL_LVALUE
				)
					tmp |= FOREACH_RIGHT_GLOBAL;
				else if (expr->r.expr->v.number == F_REF_LVALUE
				)
					tmp |= FOREACH_REF;
			}
			ins_byte(tmp);
			ins_byte(expr->l.expr->l.number);
			if (expr->r.expr)
				ins_byte(expr->r.expr->l.number);
		}
			break;
		case NODE_CASE_NUMBER:
		case NODE_CASE_STRING:
			if (expr->v.expr)
			{
				parse_node_t *other = expr->v.expr;
				expr->v.number = 1;
				other->l.expr = expr->l.expr;
				other->v.number = CURRENT_PROGRAM_SIZE;
				expr->l.expr = other;
			}
			else
			{
				expr->v.number = CURRENT_PROGRAM_SIZE;
			}
			end_pushes();
			break;
		case NODE_DEFAULT:
			expr->v.number = CURRENT_PROGRAM_SIZE;
			end_pushes();
			break;
		case NODE_SWITCH_STRINGS:
		case NODE_SWITCH_NUMBERS:
		case NODE_SWITCH_DIRECT:
		case NODE_SWITCH_RANGES:
		{
			int addr, last_break;
			parse_node_t *sub = expr->l.expr;
			parse_node_t *save_switch_breaks = branch_list[CJ_BREAK_SWITCH];

			i_generate_node(sub);
			branch_list[CJ_BREAK_SWITCH] = 0;
			end_pushes();
			ins_byte(F_SWITCH);
			addr = CURRENT_PROGRAM_SIZE;
			/* all these are addresses in here are relative to addr now,
			 * which is also the value of pc in f_switch().  Note that
			 * addr is one less than it was in older drivers.
			 */
			ins_byte(0xff); /* kind of table */
			ins_short(0); /* address of table */
			ins_short(0); /* end of table */
			ins_short(0); /* default address */
			i_generate_node(expr->r.expr);
			if (expr->v.expr && expr->v.expr->kind == NODE_DEFAULT)
			{
				upd_short(addr + 5, expr->v.expr->v.number - addr, "switch");
				expr->v.expr = expr->v.expr->l.expr;
			}
			else
			{
				upd_short(addr + 5, CURRENT_PROGRAM_SIZE - addr, "switch");
			}
			/* just in case the last case doesn't have a break */
			end_pushes();
			ins_byte(F_BRANCH);
			last_break = CURRENT_PROGRAM_SIZE;
			ins_short(0);
			/* build table */
			upd_short(addr + 1, CURRENT_PROGRAM_SIZE - addr, "switch");
#ifdef BINARIES
			if (pragmas & PRAGMA_SAVE_BINARY)
			{
				if (expr->kind == NODE_SWITCH_STRINGS)
				{
					short sw;
					sw = addr - 1;
					add_to_mem_block(A_PATCH, (char *)&sw, sizeof sw);
				}
			}
#endif
			if (expr->kind == NODE_SWITCH_DIRECT)
			{
				parse_node_t *pn = expr->v.expr;
				while (pn)
				{
					ins_rel_short(pn->v.number - addr);
					pn = pn->l.expr;
				}
				ins_int(expr->v.expr->r.number);
				mem_block[A_PROGRAM].block[addr] = (char) 0xfe;
			}
			else
			{
				int table_size = 0;
				int power_of_two = 1;
				int i = 0;
				parse_node_t *pn = expr->v.expr;

				while (pn)
				{
					if (expr->kind == NODE_SWITCH_STRINGS)
					{
						if (pn->r.number)
						{
							INS_POINTER(
							        (POINTER_INT) PROG_STRING(pn->r.number));
						}
						else
							INS_POINTER((POINTER_INT) 0);
					}
					else
						INS_POINTER((POINTER_INT) pn->r.expr);
					if (pn->v.number == 1)
						ins_short(1);
					else
						ins_rel_short(pn->v.number - addr);
					pn = pn->l.expr;
					table_size += 1;
				}
				while ((power_of_two << 1) <= table_size)
				{
					power_of_two <<= 1;
					i++;
				}
				if (expr->kind != NODE_SWITCH_STRINGS)
					mem_block[A_PROGRAM].block[addr] = (char) (0xf0 + i);
				else
					mem_block[A_PROGRAM].block[addr] = (char) (i * 0x10 + 0x0f);
			}
			i_update_branch_list(branch_list[CJ_BREAK_SWITCH], "switch break");
			branch_list[CJ_BREAK_SWITCH] = save_switch_breaks;
			upd_short(last_break, CURRENT_PROGRAM_SIZE - last_break,
			        "switch break");
			upd_short(addr + 3, CURRENT_PROGRAM_SIZE - addr, "switch");
			break;
		}
		case NODE_CATCH:
		{
			int addr;

			end_pushes();
			ins_byte(F_CATCH);
			addr = CURRENT_PROGRAM_SIZE;
			ins_short(0);
			i_generate_node(expr->r.expr);
			ins_byte(F_END_CATCH);
			upd_short(addr, CURRENT_PROGRAM_SIZE - addr, "catch");
			break;
		}
		case NODE_TIME_EXPRESSION:
		{
			end_pushes();
			ins_byte(F_TIME_EXPRESSION);
			i_generate_node(expr->r.expr);
			ins_byte(F_END_TIME_EXPRESSION);
			break;
		}
		case NODE_LVALUE_EFUN:
			i_generate_node(expr->l.expr);
			generate_lvalue_list(expr->r.expr);
			break;
		case NODE_FUNCTION_CONSTRUCTOR:
			if (expr->r.expr)
			{
				generate_expr_list(expr->r.expr);
				end_pushes();
				ins_byte(F_AGGREGATE);
				ins_short(expr->r.expr->kind);
			}
			else
			{
				end_pushes();
				ins_byte(F_CONST0);
			}
			end_pushes();

			ins_byte(F_FUNCTION_CONSTRUCTOR);
			ins_byte(expr->v.number & 0xff);

			switch (expr->v.number & 0xff)
			{
				case FP_SIMUL:
					ins_short(expr->v.number >> 8);
					break;
				case FP_LOCAL:
					ins_short(comp_def_index_map[expr->v.number >> 8]);
					break;
				case FP_EFUN:
					ins_short(predefs[expr->v.number >> 8].token);
					break;
				case FP_FUNCTIONAL:
				case FP_FUNCTIONAL | FP_NOT_BINDABLE:
				{
					int addr, save_current_num_values = current_num_values;
					ins_byte(expr->v.number >> 8);
					addr = CURRENT_PROGRAM_SIZE;
					ins_short(0);
					current_num_values = expr->r.expr ? expr->r.expr->kind : 0;
					i_generate_node(expr->l.expr);
					current_num_values = save_current_num_values;
					end_pushes();
					ins_byte(F_RETURN);
					upd_short(addr, CURRENT_PROGRAM_SIZE - addr - 2,
					        "function pointer");
					break;
				}
			}
			break;
		case NODE_ANON_FUNC:
		{
			int addr;
			int save_fd = foreach_depth;

			foreach_depth = 0;
			end_pushes();
			ins_byte(F_FUNCTION_CONSTRUCTOR);
			if (expr->v.number & 0x10000)
				ins_byte(FP_ANONYMOUS | FP_NOT_BINDABLE);
			else
				ins_byte(FP_ANONYMOUS);
			ins_byte(expr->v.number & 0xff);
			ins_byte(expr->l.number);
			addr = CURRENT_PROGRAM_SIZE;
			ins_short(0);
			i_generate_node(expr->r.expr);
			upd_short(addr, CURRENT_PROGRAM_SIZE - addr - 2,
			        "function pointer");
			foreach_depth = save_fd;
			break;
		}
		case NODE_EFUN:
		{
			int novalue_used = expr->v.number & NOVALUE_USED_FLAG;
			int f = expr->v.number & ~NOVALUE_USED_FLAG;

			generate_expr_list(expr->r.expr);
			end_pushes();
			if (f < ONEARG_MAX)
			{
				ins_byte(f);
			}
			else
			{
				/* max_arg == -1 must use F_EFUNV so that varargs expansion works*/
				if (expr->l.number < 4 && instrs[f].max_arg != -1)
					ins_byte(F_EFUN0 + expr->l.number);
				else
				{
					ins_byte(F_EFUNV);
					ins_byte(expr->l.number);
				}
				ins_byte(f - ONEARG_MAX);
			}
			if (novalue_used)
			{
				/* the value of a void efun was used.  Put in a zero. */
				ins_byte(F_CONST0);
			}
			break;
			default:
			fatal("Unknown node %i in i_generate_node.\n", expr->kind);
		}
	}
}

static void i_generate_loop(int test_first, parse_node_t * block,
        parse_node_t * inc, parse_node_t * test)
{
	parse_node_t *save_breaks = branch_list[CJ_BREAK];
	parse_node_t *save_continues = branch_list[CJ_CONTINUE];
	int forever = node_always_true(test);
	int pos;

	if (test_first == 2)
		foreach_depth++;
	branch_list[CJ_BREAK] = branch_list[CJ_CONTINUE] = 0;
	end_pushes();
	if (!forever && test_first)
		i_generate_forward_branch(F_BRANCH);
	pos = CURRENT_PROGRAM_SIZE;
	i_generate_node(block);
	i_update_branch_list(branch_list[CJ_CONTINUE], "continue");
	if (inc)
		i_generate_node(inc);
	if (!forever && test_first)
		i_update_forward_branch("loop");
	if (test
	        && (test->v.number == F_LOOP_COND_LOCAL
	                || test->v.number == F_LOOP_COND_NUMBER
	                || test->v.number == F_NEXT_FOREACH))
	{
		i_generate_node(test);
		ins_short(CURRENT_PROGRAM_SIZE - pos);
	}
	else
		i_branch_backwards(generate_conditional_branch(test), pos);
	i_update_branch_list(branch_list[CJ_BREAK], "break");
	branch_list[CJ_BREAK] = save_breaks;
	branch_list[CJ_CONTINUE] = save_continues;
	if (test_first == 2)
		foreach_depth--;
}

static void i_generate_if_branch(parse_node_t * node, int invert)
{
	int generate_both = 0;
	int branch = (invert ? F_BRANCH_WHEN_NON_ZERO : F_BRANCH_WHEN_ZERO);

	switch (node->kind)
	{
		case NODE_UNARY_OP:
			if (node->v.number == F_NOT)
			{
				i_generate_if_branch(node->r.expr, !invert);
				return;
			}
			break;
		case NODE_BINARY_OP:
			switch (node->v.number)
			{
				case F_EQ:
					generate_both = 1;
					branch = (invert ? F_BRANCH_EQ : F_BRANCH_NE);
					break;
				case F_GE:
					if (invert)
					{
						generate_both = 1;
						branch = F_BRANCH_GE;
					}
					break;
				case F_LE:
					if (invert)
					{
						generate_both = 1;
						branch = F_BRANCH_LE;
					}
					break;
				case F_LT:
					if (!invert)
					{
						generate_both = 1;
						branch = F_BRANCH_GE;
					}
					break;
				case F_GT:
					if (!invert)
					{
						generate_both = 1;
						branch = F_BRANCH_LE;
					}
					break;
				case F_NE:
					generate_both = 1;
					branch = (invert ? F_BRANCH_NE : F_BRANCH_EQ);
					break;
			}
	}
	if (generate_both)
	{
		i_generate_node(node->l.expr);
		i_generate_node(node->r.expr);
	}
	else
	{
		i_generate_node(node);
	}
	i_generate_forward_branch(branch);
}

void i_generate_inherited_init_call(int index, short f)
{
	end_pushes();
	ins_byte(F_CALL_INHERITED);
	ins_byte(index);
	ins_short(f);
	ins_byte(0);
	ins_byte(F_POP_VALUE);
}

void i_generate_forward_branch(char b)
{
	end_pushes();
	ins_byte(b);
	if (nforward_branches == nforward_branches_max)
	{
		nforward_branches_max += 10;
		forward_branches = RESIZE(forward_branches, nforward_branches_max, int,
				TAG_COMPILER, "forward_branches");
	}

	forward_branches[nforward_branches++] = CURRENT_PROGRAM_SIZE;
	ins_short(0);
}

void i_update_forward_branch(char * what)
{
	end_pushes();
	nforward_branches--;
	upd_short(forward_branches[nforward_branches],
	        CURRENT_PROGRAM_SIZE - forward_branches[nforward_branches], what);
}

void i_update_forward_branch_links(char kind, parse_node_t * link_start)
{
	int i;

	end_pushes();
	nforward_branches--;
	upd_short(forward_branches[nforward_branches],
	        CURRENT_PROGRAM_SIZE - forward_branches[nforward_branches],
	        "&& or ||");
	do
	{
		i = link_start->v.number;
		upd_byte(i - 1, kind);
		upd_short(i, CURRENT_PROGRAM_SIZE - i, "&& or ||");
		link_start = link_start->l.expr;
	}
	while (link_start->kind == NODE_BRANCH_LINK);
}

void i_branch_backwards(char b, int addr)
{
	end_pushes();
	if (b)
	{
		if (b != F_WHILE_DEC
		)
			ins_byte(b);
		ins_short(CURRENT_PROGRAM_SIZE - addr);
	}
}

static void i_update_branch_list(parse_node_t * bl, char * what)
{
	int current_size;

	end_pushes();
	current_size = CURRENT_PROGRAM_SIZE;

	while (bl)
	{
		upd_short(bl->l.number, current_size - bl->l.number, what);
		bl = bl->v.expr;
	}
}

void i_generate_else()
{
	int old;

	old = forward_branches[nforward_branches - 1];

	/* set up a new branch to after the end of the if */
	end_pushes();
	ins_byte(F_BRANCH);
	forward_branches[nforward_branches - 1] = CURRENT_PROGRAM_SIZE;
	ins_short(0);

	/* update the old branch to point to this point */
	upd_short(old, CURRENT_PROGRAM_SIZE - old, "if");
}

void i_initialize_parser()
{
	foreach_depth = 0;

	if (!forward_branches)
	{
		forward_branches = CALLOCATE(10, int, TAG_COMPILER, "forward_branches");
		nforward_branches_max = 10;
	}
	nforward_branches = 0;

	branch_list[CJ_BREAK] = 0;
	branch_list[CJ_BREAK_SWITCH] = 0;
	branch_list[CJ_CONTINUE] = 0;

	prog_code = mem_block[A_PROGRAM].block;
	prog_code_max = mem_block[A_PROGRAM].block + mem_block[A_PROGRAM].max_size;

	line_being_generated = 0;
	last_size_generated = 0;
}

void i_uninitialize_parser()
{
#ifdef DEBUGMALLOC_EXTENSIONS
	/*
	 * The intention is to keep the allocated space for forward branches
	 * hanging around between compiles so we don't have to keep allocating it.
	 * The problem is that with DEBUGMALLOC_EXENTIONS compiled in, it'll be
	 * reported as a memory leak, so clean it up here.  Yes, it's a performance
	 * hit, but we're trying to debug not run a race, so deal with it.
	 * -- Marius, 11-Aug-2000
	 */
	if (forward_branches)
	{
		FREE(forward_branches);
		forward_branches = 0;
	}
#endif
}

void i_generate_final_program(int x)
{
	if (!x)
	{
		UPDATE_PROGRAM_SIZE;
		/* This needs work
		 * if (pragmas & PRAGMA_OPTIMIZE)
		 *     optimize_icode(0, 0, 0);
		 */
		save_file_info(current_file_id, current_line - current_line_saved);
		switch_to_line(-1); /* generate line numbers for the end */
	}
}

/* Currently, this procedure handles:
 * - jump threading
 */
void optimize_icode(char * start, char * pc, char * end)
{
	int instr;
	if (start == 0)
	{
		/* we don't optimize the initializer block right now b/c all the
		 * stuff we do (jump threading, etc) can't occur there.
		 */
		start = mem_block[A_PROGRAM].block;
		pc = start;
		end = pc + mem_block[A_PROGRAM].current_size;
		if (*pc == 0)
		{
			/* no initializer jump */
			pc += 3;
		}
	}
	while (pc < end)
	{
		switch (instr = EXTRACT_UCHAR(pc++))
		{
			case F_NUMBER:
			case F_REAL:
			case F_CALL_INHERITED:
				pc += 4;
				break;
			case F_SIMUL_EFUN:
			case F_CALL_FUNCTION_BY_ADDRESS:
				pc += 3;
				break;
			case F_BRANCH:
			case F_BRANCH_WHEN_ZERO:
			case F_BRANCH_WHEN_NON_ZERO:
			case F_BBRANCH:
			case F_BBRANCH_WHEN_ZERO:
			case F_BBRANCH_WHEN_NON_ZERO:
			{
				char *tmp;
				short sarg;
				/* thread jumps */
				COPY_SHORT(&sarg, pc);
				if (instr > F_BRANCH
				)
					tmp = pc - sarg;
				else
					tmp = pc + sarg;
				sarg = 0;
				while (1)
				{
					if (EXTRACT_UCHAR(tmp) == F_BRANCH)
					{
						COPY_SHORT(&sarg, tmp + 1);
						tmp += sarg + 1;
					}
					else if (EXTRACT_UCHAR(tmp) == F_BBRANCH)
					{
						COPY_SHORT(&sarg, tmp + 1);
						tmp -= sarg - 1;
					}
					else
						break;
				}
				if (!sarg)
				{
					pc += 2;
					break;
				}
				/* be careful; in the process of threading a forward jump
				 * may have changed to a reverse one or vice versa
				 */
				if (tmp > pc)
				{
					if (instr > F_BRANCH)
					{
						pc[-1] -= 3; /* change to forward branch */
					}
					sarg = tmp - pc;
				}
				else
				{
					if (instr <= F_BRANCH)
					{
						pc[-1] += 3; /* change to backwards branch */
					}
					sarg = pc - tmp;
				}STORE_SHORT(pc, sarg);
				break;
			}
#ifdef F_LOR
			case F_LOR:
			case F_LAND:
			{
				char *tmp;
				short sarg;
				/* thread jumps */
				COPY_SHORT(&sarg, pc);
				tmp = pc + sarg;
				sarg = 0;
				while (1)
				{
					if (EXTRACT_UCHAR(tmp) == F_BRANCH)
					{
						COPY_SHORT(&sarg, tmp + 1);
						tmp += sarg + 1;
					}
					else if (EXTRACT_UCHAR(tmp) == F_BBRANCH)
					{
						COPY_SHORT(&sarg, tmp + 1);
						tmp -= sarg - 1;
					}
					else
						break;
				}
				if (!sarg)
				{
					pc += 2;
					break;
				}
				/* be careful; in the process of threading a forward jump
				 * may have changed to a reverse one or vice versa
				 */
				if (tmp > pc)
				{
					sarg = tmp - pc;
				}
				else
				{
#ifdef DEBUG
					fprintf(stderr,"Optimization failed; can't || or && backwards.\n");
#endif
					pc += 2;
					break;
				}STORE_SHORT(pc, sarg);
				break;
			}
#endif
			case F_CATCH:
			case F_AGGREGATE:
			case F_AGGREGATE_ASSOC:
			case F_STRING:
#ifdef F_JUMP_WHEN_ZERO
				case F_JUMP_WHEN_ZERO:
				case F_JUMP_WHEN_NON_ZERO:
#endif
#ifdef F_JUMP
				case F_JUMP:
#endif
				pc += 2;
				break;
			case F_GLOBAL_LVALUE:
			case F_GLOBAL:
			case F_SHORT_STRING:
			case F_LOOP_INCR:
			case F_WHILE_DEC:
			case F_LOCAL:
			case F_LOCAL_LVALUE:
			case F_REF:
			case F_REF_LVALUE:
			case F_SSCANF:
			case F_PARSE_COMMAND:
			case F_BYTE:
			case F_NBYTE:
				pc++;
				break;
			case F_FUNCTION_CONSTRUCTOR:
				switch (EXTRACT_UCHAR(pc++))
				{
					case FP_SIMUL:
					case FP_LOCAL:
						pc += 2;
						break;
					case FP_FUNCTIONAL:
					case FP_FUNCTIONAL | FP_NOT_BINDABLE:
						pc += 3;
						break;
					case FP_ANONYMOUS:
					case FP_ANONYMOUS | FP_NOT_BINDABLE:
						pc += 4;
						break;
					case FP_EFUN:
						pc += 2;
						break;
				}
				break;
			case F_SWITCH:
			{
				unsigned short stable, etable;
				pc++; /* table type */
				LOAD_SHORT(stable, pc);
				LOAD_SHORT(etable, pc);
				pc += 2; /* def */
				DEBUG_CHECK(stable < pc - start || etable < pc - start
						|| etable < stable,
						"Error in switch table found while optimizing\n");
				/* recursively optimize the inside of the switch */
				optimize_icode(start, pc, start + stable);
				pc = start + etable;
				break;
			}
			case F_EFUN0:
			case F_EFUN1:
			case F_EFUN2:
			case F_EFUN3:
			case F_EFUNV:
				instr = EXTRACT_UCHAR(pc++) + ONEARG_MAX;
			default:
				if ((instr >= BASE)
				        && (instrs[instr].min_arg != instrs[instr].max_arg))
					pc++;
		}
	}
}

